#include "Define.h"

#include <cstdio>
#include <cstdlib>
#include <cmath>

#include <vector>
#include <fstream>
#include <string>

using namespace std;

#include <GL/glew.h>
#include <GL/glfw.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

using namespace glm;

#include "GLWindow.h"

GLWindow::GLWindow()
{
}

GLWindow::~GLWindow()
{
}

bool	GLWindow::init()
{
	initGLFW();
	initGLEW();

	glfwSetWindowTitle( "Computer Graphics Assignment 2" );

	glfwEnable( GLFW_STICKY_KEYS );

	initGLSetting();

	glGenVertexArrays(1, &VertexArrayID);
	glBindVertexArray(VertexArrayID);

	PhongShadingShader.init("TransformVertexShader.vertexshader", "TextureFragmentShader.fragmentshader");
	PhongShadingShader.pushUniformLocation("MVP");
	PhongShadingShader.pushUniformLocation("V");
	PhongShadingShader.pushUniformLocation("M");

	// Load Texture
	texture.init("earth.rgb");
	PhongShadingShader.pushUniformLocation("myTextureSampler");

	int stack = 32, slice = 32;

	for(int i=0; i<=stack; i++)
	{
		float rho[2], r[2], x[2], y[2], z[2];
		float theta;
		float v[2], u;

		rho[0] = ( 90 - (  i  *180.0f / stack) ) / 180 * 3.141592;
		rho[1] = ( 90 - ((i+1)*180.0f / stack) ) / 180 * 3.141592;
		
		y[0]   = sinf(rho[0]);	r[0] = cosf(rho[0]);
		y[1]   = sinf(rho[1]);	r[1] = cosf(rho[1]);

		v[0]   = i/(double)stack;		v[1] = (i+1)/(double)stack;

		for(int j=0; j<=slice; j++)
		{
			theta = j/(double)slice * 3.141592 * 2;
			u	  = j/(double)slice;

			x[0]  = sinf(theta)*r[0];	z[0] = cosf(theta)*r[0];
			x[1]  = sinf(theta)*r[1];	z[1] = cosf(theta)*r[1];

			g_vertex_buffer_data.push_back(x[0]);
			g_vertex_buffer_data.push_back(y[0]);
			g_vertex_buffer_data.push_back(z[0]);
			g_vertex_buffer_data.push_back(x[1]);
			g_vertex_buffer_data.push_back(y[1]);
			g_vertex_buffer_data.push_back(z[1]);

			g_normal_buffer_data.push_back(x[0]);
			g_normal_buffer_data.push_back(y[0]);
			g_normal_buffer_data.push_back(z[0]);
			g_normal_buffer_data.push_back(x[1]);
			g_normal_buffer_data.push_back(y[1]);
			g_normal_buffer_data.push_back(z[1]);

			g_uv_buffer_data.push_back(u);
			g_uv_buffer_data.push_back(v[0]);
			g_uv_buffer_data.push_back(u);
			g_uv_buffer_data.push_back(v[1]);
		}
	}

	glGenBuffers(1, &vertexbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
	glBufferData(GL_ARRAY_BUFFER, g_vertex_buffer_data.size() * sizeof(GLfloat), &g_vertex_buffer_data.front(), GL_STATIC_DRAW);

	glGenBuffers(1, &uvbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
	glBufferData(GL_ARRAY_BUFFER, g_uv_buffer_data.size() * sizeof(GLfloat), &g_uv_buffer_data.front(), GL_STATIC_DRAW);

	glGenBuffers(1, &normalbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
	glBufferData(GL_ARRAY_BUFFER, g_normal_buffer_data.size()*sizeof(GLfloat), &g_normal_buffer_data.front(), GL_STATIC_DRAW);

	PhongShadingShader.pushUniformLocation("LightPosition_worldspace");

	angle = 0.f;

	return true;
}

void	GLWindow::destroy()
{
	destroyGLSetting();

	glDeleteBuffers(1, &vertexbuffer);
	glDeleteBuffers(1, &uvbuffer);
	glDeleteBuffers(1, &normalbuffer);

	PhongShadingShader.destroy();

	glDeleteVertexArrays(1, &VertexArrayID);

	destroyGLEW();
	destroyGLFW();
}

void	GLWindow::initGLSetting()
{
	glClearColor(0.0f, 0.0f, 0.4f, 0.0f);

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS); 

	glEnable(GL_CULL_FACE);
}

void	GLWindow::destroyGLSetting()
{
}

void	GLWindow::initGLFW()
{
	if( !glfwInit() )
	{
		fprintf( stderr, "Failed to initialize GLFW. if failed, check your video driver compatitiable OpenGL 4.3\n" );
		std::exit(EXIT_FAILURE);
	}

	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 4);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	if( !glfwOpenWindow( 1024, 768, 8,8,8,8, 32,0, GLFW_WINDOW ) )
	{
		fprintf( stderr, "Failed to open GLFW window.\n" );
		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}
}

void	GLWindow::initGLEW()
{
	glewExperimental = true;
	if (glewInit() != GLEW_OK) {
		fprintf(stderr, "Failed to initialize GLEW\n");
		std::exit(EXIT_FAILURE);
	}
}

void	GLWindow::destroyGLFW()
{
	glfwTerminate();
}

void	GLWindow::destroyGLEW()
{
}

void	GLWindow::frameMove()
{
}

void	GLWindow::frameRender()
{
}

void	GLWindow::mainLoop()
{
	do
	{
		angle += 360.f/60.f/2.f;

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		PhongShadingShader.useThisShader();

		// transformation
		glm::mat4 ProjectionMatrix = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
		glm::mat4 ViewMatrix       = glm::lookAt(
									glm::vec3(0,0,7),
									glm::vec3(0,0,0),
									glm::vec3(0,1,0)
							   );
	
		glm::mat4 ModelMatrix      = glm::mat4(1.0f);
		ModelMatrix = glm::rotate(ModelMatrix, 21.f, glm::vec3(0, 0, 1));
		ModelMatrix = glm::rotate(ModelMatrix, angle, glm::vec3(0, 1, 0));
	
		glm::mat4 MVP        = ProjectionMatrix * ViewMatrix * ModelMatrix;

		PhongShadingShader.pushUniformMatrix4fv("MVP",	glm::value_ptr(MVP));
		PhongShadingShader.pushUniformMatrix4fv("M",	glm::value_ptr(ModelMatrix));
		PhongShadingShader.pushUniformMatrix4fv("V",	glm::value_ptr(ViewMatrix));

		// light
		glm::vec3 lightPos = glm::vec3(1, 1, 2);
		PhongShadingShader.pushUniform3f("LightPosition_worldspace", lightPos);

		// texture
		texture.activate(0);
		PhongShadingShader.pushUniform1i("myTextureSampler", 0);

		glEnableVertexAttribArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
		glVertexAttribPointer(
			0,
			3,
			GL_FLOAT,
			GL_FALSE,
			0,
			(void*)0
		);

		glEnableVertexAttribArray(1);
		glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
		glVertexAttribPointer(
			1,
			2, 
			GL_FLOAT,
			GL_FALSE,
			0,
			(void*)0
		);

		glEnableVertexAttribArray(2);
		glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
		glVertexAttribPointer(
			2,
			3,
			GL_FLOAT,
			GL_FALSE,
			0,
			(void*)0
		);

		glDrawArrays(GL_TRIANGLE_STRIP, 0, g_vertex_buffer_data.size());

		glDisableVertexAttribArray(0);
		glDisableVertexAttribArray(1);
		glDisableVertexAttribArray(2);

		glfwSwapBuffers();

	}
	while( glfwGetKey( GLFW_KEY_ESC ) != GLFW_PRESS &&
		   glfwGetWindowParam( GLFW_OPENED ) );
}